/*
 * $Id: log.c 111 2010-11-30 09:04:14Z hyunghwan.chung $
 *
   Copyright 2005-2009 Chung, Hyung-Hwan.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an  BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

#include <xp/utl/log.h>
#include <xp/bas/memory.h>
#include <xp/bas/stdarg.h>
#include <xp/bas/string.h>
#include <xp/bas/mstring.h>
#include <xp/bas/time.h>
#include <xp/bas/sio.h>
#include <xp/bas/locale.h>
#include <xp/bas/stdio.h>

#define MASK_PRIORITY 0x00FF
#define MASK_TARGET   0xFF00

#ifdef _WIN32
	/* TODO: windows event log */
#else
	#include <syslog.h>
#endif

static int __report (
	const xp_char_t* tm, xp_sio_t* sio,
	const xp_char_t* ident, const xp_char_t* msg);

static const xp_char_t* __priority_names[] =
{
	XP_T("panic"),
	XP_T("alert"),
	XP_T("critical"),
	XP_T("error"),
	XP_T("warning"),
	XP_T("notice"),
	XP_T("info"),
	XP_T("debug"),
	XP_NULL
};

#ifndef LOG_EMERG
#	define LOG_EMERG  0 
#endif
#ifndef LOG_ALERT
#	define LOG_ALERT  1  
#endif
#ifndef LOG_CRIT
#	define LOG_CRIT   2
#endif
#ifndef LOG_ERR
#	define LOG_ERR         3
#endif
#ifndef LOG_WARNING
#	define LOG_WARNING     4
#endif
#ifndef LOG_NOTICE
#	define LOG_NOTICE      5
#endif
#ifndef LOG_INFO
#	define LOG_INFO        6
#endif
#ifndef LOG_DEBUG
#	define LOG_DEBUG       7
#endif

static int __syslog_priority[] =
{
	LOG_EMERG,
	LOG_ALERT,
	LOG_CRIT,
	LOG_ERR,
	LOG_WARNING,
	LOG_NOTICE,
	LOG_INFO,
	LOG_DEBUG
};

#ifdef _WIN32
/* TODO: windows event logging */
#else

struct syslog_fac_info_t
{
	const xp_char_t* name;
	xp_log_fac_t     code;
};

static struct syslog_fac_info_t __syslog_fac_info[] =
{
	{ XP_T("kern"),     XP_LOG_KERN },
	{ XP_T("user"),     XP_LOG_USER },
	{ XP_T("mail"),     XP_LOG_MAIL },
	{ XP_T("daemon"),   XP_LOG_DAEMON },
	{ XP_T("auth"),     XP_LOG_AUTH },
	{ XP_T("syslogd"),  XP_LOG_SYSLOGD },
	{ XP_T("lpr"),      XP_LOG_LPR },
	{ XP_T("news"),     XP_LOG_NEWS },
	{ XP_T("uucp"),     XP_LOG_UUCP },
	{ XP_T("cron"),     XP_LOG_CRON },
	{ XP_T("authpriv"), XP_LOG_AUTHPRIV },
	{ XP_T("ftp"),      XP_LOG_FTP },
	{ XP_T("local0"),   XP_LOG_LOCAL0 },
	{ XP_T("local1"),   XP_LOG_LOCAL1 },
	{ XP_T("local2"),   XP_LOG_LOCAL2 },
	{ XP_T("local3"),   XP_LOG_LOCAL3 },
	{ XP_T("local4"),   XP_LOG_LOCAL4 },
	{ XP_T("local5"),   XP_LOG_LOCAL5 },
	{ XP_T("local6"),   XP_LOG_LOCAL6 },
	{ XP_T("local7"),   XP_LOG_LOCAL7 },

	{ XP_NULL,          0 }
};

#endif

xp_log_t* xp_log_open (xp_log_t* log, const xp_char_t* ident, int type, ...)
{
	xp_va_list ap;

	if (log == XP_NULL) 
	{
		log = (xp_log_t*) xp_malloc (XP_SIZEOF(xp_log_t));
		if (log == XP_NULL) return XP_NULL;

		xp_memset (log, 0, XP_SIZEOF(*log));
		log->__dynamic = xp_true;
	}
	else 
	{
		xp_memset (log, 0, XP_SIZEOF(*log));
		log->__dynamic = xp_false;
	}

	log->type = type & MASK_PRIORITY;

	if (type & XP_LOG_FILE) 
	{
		xp_va_start (ap, type);
		xp_strxcpy (log->target.file, 
			xp_countof(log->target.file), xp_va_arg(ap, const xp_char_t*));
		xp_va_end (ap);
		log->type |= XP_LOG_FILE;
	}

	if (type & XP_LOG_CONSOLE) log->type |= XP_LOG_CONSOLE;
	if (type & XP_LOG_SYSLOG) log->type |= XP_LOG_SYSLOG;

	if (type & XP_LOG_SYSLOG_R4)
	{
		xp_sckadrin4_t host;

		xp_va_start (ap, type);
		xp_memcpy (&host, va_arg(ap, const struct sockaddr_in*), XP_SIZEOF(host));
		xp_va_end (ap);

		/* address family should be AF_INET */
		if (host.sin_family == AF_INET)
		{
			xp_sckhnd_t sock;
			sock = xp_sckopen (AF_INET, SOCK_DGRAM, 0);

			if (sock != -1) 
			{
				log->target.syslog_r4.host = host;
				log->target.syslog_r4.sock = sock;
				log->type |= XP_LOG_SYSLOG_R4;
			}
		}
	}

	if (ident == XP_NULL) log->ident[0] = XP_T('\0');
	else xp_strxcpy (log->ident, XP_COUNTOF(log->ident), ident);

	if (xp_mtx_open(&log->mtx) == XP_NULL) 
	{
		if (log->__dynamic) xp_free (log);
		return XP_NULL;
	}

#ifdef _WIN32
	/* TODO: windows event logging */
#else
	log->syslog_facility = XP_LOG_USER;
#endif

	return log;
}

void xp_log_close (xp_log_t* log)
{
	if (log->type & XP_LOG_SYSLOG_R4)
	{
		xp_sckclose (log->target.syslog_r4.sock);
	}

	if (log->type & XP_LOG_CONSOLE) 
	{
		/* TODO: need mtx here? */
		xp_sio_flush (xp_sio_err);
	}

	xp_mtx_close (&log->mtx);
	if (log->__dynamic) xp_free (log);
}

int xp_log_clear (xp_log_t* log)
{
	if (log->type & XP_LOG_FILE) {
		xp_sio_t sio;
		if (xp_sio_open(&sio, 
			log->target.file, XP_T("w")) == XP_NULL) return -1;
		xp_sio_close (&sio);
	}

	return 0;
}

void xp_log_setident (xp_log_t* log, const xp_char_t* ident)
{
	if (ident == XP_NULL) log->ident[0] = XP_T('\0');
	else xp_strxcpy (log->ident, xp_countof(log->ident), ident);
}

void xp_log_settarget (xp_log_t* log, int type)
{
	if ((log->type & XP_LOG_SYSLOG_R4) != 0 &&
	    (type & XP_LOG_SYSLOG_R4) == 0) 
	{
		xp_sckclose (log->target.syslog_r4.sock);
	}

	if ((log->type & XP_LOG_SYSLOG_R4) == 0 &&
	    (type & XP_LOG_SYSLOG_R4) != 0) 
	{
		int sock;
		sock = xp_sckopen (AF_INET, SOCK_DGRAM, 0);
		if (sock == -1) type &= ~XP_LOG_SYSLOG_R4;	
		else log->target.syslog_r4.sock = sock;
	}

	log->type &= MASK_PRIORITY; /* preserve the level */
	if (type & XP_LOG_FILE) log->type |= XP_LOG_FILE;
	if (type & XP_LOG_CONSOLE) log->type |= XP_LOG_CONSOLE;
	if (type & XP_LOG_SYSLOG) log->type |= XP_LOG_SYSLOG;
	if (type & XP_LOG_SYSLOG_R4) log->type |= XP_LOG_SYSLOG_R4;
}

void xp_log_settargetfile (xp_log_t* log, const xp_char_t* file)
{
	xp_strxcpy (log->target.file, xp_countof(log->target.file), file);
}

void xp_log_settargetsyslogr4host (xp_log_t* log, const xp_sckadrin4_t* addr)
{
	log->target.syslog_r4.host = *addr;
}

void xp_log_setpriority (xp_log_t* log, int priority)
{
	log->type = (log->type & MASK_TARGET) | (priority & MASK_PRIORITY);
}

int xp_log_setprioritybyname (xp_log_t* log, const xp_char_t* name)
{
	const xp_char_t** p = __priority_names;

	while (*p != XP_NULL) 
	{
		if (xp_strcmp (*p, name) == 0) 
		{
			xp_log_setpriority (log, (int)(p - __priority_names));
			return 0;
		}

		p++;
	}

	return -1;
}

void xp_log_setsyslogfacility (xp_log_t* log, xp_log_fac_t facility)
{
#ifndef _WIN32
	log->syslog_facility = facility;
#endif
}

int xp_log_setsyslogfacilitybyname (xp_log_t* log, const xp_char_t* name)
{
#ifdef _WIN32
	/* do nothing */
	return 0;
#else
	struct syslog_fac_info_t* f = __syslog_fac_info;

	while (f->name != XP_NULL) 
	{
		if (xp_strcmp (f->name, name) == 0) 
		{
			log->syslog_facility = f->code;
			return 0;
		}

		f++;
	}

	return -1;
#endif
}

int xp_log_getpriority (xp_log_t* log)
{
	return log->type & MASK_PRIORITY;
}	

const xp_char_t* xp_log_getpriorityname (xp_log_t* log)
{
	int pri = log->type & MASK_PRIORITY;

	if (pri < 0 || pri >= xp_countof(__priority_names)) return XP_NULL;
	return __priority_names[pri];
}

int xp_log_gettarget (xp_log_t* log)
{
	return log->type & MASK_TARGET;
}

int xp_log_report (xp_log_t* log, 
	const xp_char_t* ident, int pri, const xp_char_t* fmt, ...)
{
	xp_time_t now;
	xp_tm_t cnow;
	xp_char_t tm[64];
	xp_va_list ap;
	xp_char_t idt_buf[XP_LOG_IDENT_MAX * 2 + 1];
	const xp_char_t* idt = XP_NULL;
	int n1 = 0, n2 = 0;
	/*int gmtoff_hour, gmtoff_min;*/

	if (log == XP_NULL) return 0;
	if ((log->type & MASK_TARGET) == 0) return 0;
	if (pri > (log->type & MASK_PRIORITY)) return 0;

	if (xp_gettime(&now) == -1) return -1;
	if (xp_localtime(now, &cnow) == -1) return -1;

#ifdef _WIN32
	if (xp_formattime (tm, xp_countof(tm),
		XP_T("%Y-%m-%d %H:%M:%S"), &cnow) == 0) return -1;
#else
	if (xp_formattime (tm, xp_countof(tm),
		XP_T("%Y-%m-%d %H:%M:%S %z"), &cnow) == 0) return -1;
#endif

	/*
	gmtoff_hour = cnow.gmtoff / XP_SEC_IN_HOUR;
	gmtoff_min = (cnow.gmtoff % XP_SEC_IN_HOUR) / XP_SEC_IN_MIN;
	xp_sprintf (tm, xp_countof(tm),
		XP_T("%04.4d-%02d-%02d %02d:%02d:%02d %c%02d%02d"), 
		cnow.year + XP_CTIME_BASE_YEAR, cnow.mon + 1, cnow.day, 
		cnow.hour, cnow.min, cnow.sec, 
		((cnow.gmtoff > 0)? XP_T('+'): XP_T('-')),
		gmtoff_hour, gmtoff_min);
	*/

	if (ident == XP_NULL || ident[0] == XP_T('\0')) 
	{
		if (log->ident[0] != XP_T('\0')) idt = log->ident;
	}
	else 
	{
		if (log->ident[0] != XP_T('\0')) 
		{
			xp_strxapp (
				idt_buf, xp_countof(idt_buf), log->ident, 
				XP_T("("), ident, XP_T(")"), XP_NULL);
			idt = idt_buf;
		}
		else idt = ident;
	}


	if (xp_mtx_lock (&log->mtx) == -1) return -1;

	xp_va_start (ap, fmt);
	/* TODO: some way not to truncate the message */
	xp_vsprintf(log->msgbuf, xp_countof(log->msgbuf), fmt, ap);
	xp_va_end (ap);

	if (log->type & XP_LOG_CONSOLE) 
	{
		n1 = __report (tm, xp_sio_err, idt, log->msgbuf);
	}

	if (log->type & XP_LOG_FILE) 
	{
		xp_sio_t sio;
		if (xp_sio_open(&sio, log->target.file, XP_T("a")) != XP_NULL) 
		{
			n2 = __report (tm, &sio, idt, log->msgbuf);
			xp_sio_close (&sio);
		}
		else n2 = -1;
	}

	if (log->type & XP_LOG_SYSLOG) 
	{
	#ifdef _WIN32
		/* TODO: windows event log */
	#else
		int sl_pri = (pri < xp_countof(__syslog_priority))? 
			__syslog_priority[pri]: LOG_DEBUG;

	#if defined(XP_CHAR_IS_MCHAR)
		if (idt != XP_NULL) 
			openlog (idt, 0, log->syslog_facility);
		syslog (sl_pri, "%s", log->msgbuf);
	#else
		if (idt != XP_NULL) 
		{
			xp_mchar_t idt_mb[XP_LOG_IDENT_MAX];
			xp_wcstomcs (idt, idt_mb, xp_countof(idt_mb));
			openlog (idt_mb, 0, log->syslog_facility);
		}

		/* 
		 * don't care about the maximum length when converted 
		 * to a multi-byte string. a message can be truncated 
		 * furthur due to this
		 */
		xp_wcstomcs (log->msgbuf, log->msgbuf_mb, xp_countof(log->msgbuf_mb));
		syslog (sl_pri, "%s", log->msgbuf_mb);
	#endif
		if (idt != XP_NULL) closelog ();
	#endif
	}

	/* remote syslogging for ipv4 */
	if (log->type & XP_LOG_SYSLOG_R4)  
	{
	#ifdef _WIN32
		/* TODO: windows event log */
	#else
		int sl_pri;

		sl_pri = (pri < XP_COUNTOF(__syslog_priority))? 
			__syslog_priority[pri]: LOG_DEBUG;

		xp_formattime (tm, XP_COUNTOF(tm), XP_T("%b %d %H:%M:%S"), &cnow);
		if (idt == XP_NULL) 
		{
			xp_sprintf (log->msgbuf2, XP_COUNTOF(log->msgbuf2), 
				XP_T("<%d>%s %s"), 
				(int)(log->syslog_facility | sl_pri), 
				tm, log->msgbuf);
		}
		else 
		{
			xp_sprintf (log->msgbuf2, XP_COUNTOF(log->msgbuf2), 
				XP_T("<%d>%s %s: %s"), 
				(int)(log->syslog_facility | sl_pri), 
				tm, idt, log->msgbuf);
		}

	#if defined(XP_CHAR_IS_MCHAR)
		xp_scksendto (
			log->target.syslog_r4.sock, log->msgbuf2, xp_strlen(log->msgbuf2), 0,
			(xp_sckadr_t*)&log->target.syslog_r4.host, 
			XP_SIZEOF(log->target.syslog_r4.host), -1);
	#else
		xp_wcstomcs (log->msgbuf2, log->msgbuf_mb, XP_COUNTOF(log->msgbuf_mb));

		xp_scksendto (
			log->target.syslog_r4.sock, 
			log->msgbuf_mb, xp_mstrlen(log->msgbuf_mb), 0,
			(xp_sckadr_t*)&log->target.syslog_r4.host, 
			XP_SIZEOF(log->target.syslog_r4.host), -1);
	#endif
	#endif
	}

	xp_mtx_unlock (&log->mtx);
	return (n1 == 0 && n2 == 0)? 0: -1;
}

static int __report (
	const xp_char_t* tm, xp_sio_t* sio,
	const xp_char_t* ident, const xp_char_t* msg)
{
	if (ident == XP_NULL) 
	{
		xp_sio_putsn (sio, tm, 
			XP_T(" "), msg, XP_T("\n"), XP_NULL);
	}
	else 
	{
		xp_sio_putsn (sio, tm, XP_T(" "), 
			ident, XP_T(": "), msg, XP_T("\n"), XP_NULL);
	}

	return 0;
}

const xp_char_t* xp_getlogpriorityname (int pri)
{
	if (pri < 0 || pri >= xp_countof(__priority_names)) return XP_NULL;
	return __priority_names[pri];
}
